using System.Text.Json;
using MultiAgentsClient.ChatDataStructures;
using MultiAgentsClient.Service.WorkflowEngine.BaseNodes;
using MultiAgentsShared;
using OpenAI.ObjectModels.RequestModels;
using static MultiAgentsClient.Tools.CmdToolProvider;

namespace MultiAgentsClient.Service;

public record ToolCallRequest(string AgentTypeName, IList<ToolCall> ToolCalls, int ToolCallMessageId);
public record ToolCallServiceResponse(Dictionary<string, string> ToolCallResults, int ToolCallMessageId);

public class ClientToolCallerService
{
    private static EventBus eventBus;

    public ClientToolCallerService()
    {
        eventBus = EventBus.Instance;

        eventBus.Subscribe<NodeToolCallRequest>(cmd => { Task.Run(() => ToolInvoker(cmd)); });
        eventBus.Subscribe<ToolCallRequest>(cmd => { Task.Run(() => ToolInvoker(cmd)); });
    }

    private async void ToolInvoker(NodeToolCallRequest request)
    {
        var funcParams = new Dictionary<string, object>();
        foreach (var p in request.ToolCallParams)
        {
            funcParams.Add(p.name, p.value);
        }

        var finalMsg = await ToolInvoker(request.SelectedTool, funcParams);

        eventBus.Publish(new NodeToolCallResponse(request.NodeId)
        {
            response = finalMsg
        });
    }

    private async void ToolInvoker(ToolCallRequest toolCallRequest)
    {
        var toolCallsCache = new Dictionary<string, string>();
        foreach (ToolCall toolCall in toolCallRequest.ToolCalls)
        {
            string finalMsg = "";
            List<Dictionary<string, object>>? funcParams = toolCall.FunctionCall.ParseArgumentsAdvanced();
            foreach (var param in funcParams)
            {
                finalMsg += await ToolInvoker(toolCall.FunctionCall.Name, param);
            }

            toolCallsCache.Add(toolCall.Id, $"@{toolCallRequest.AgentTypeName}\n" + finalMsg);
        }

        eventBus.Publish(new ToolCallServiceResponse(toolCallsCache, toolCallRequest.ToolCallMessageId));
    }

    private static async Task<string> ToolInvoker(string funcCallName, Dictionary<string, object>? funcParams)
    {
        try
        {
            string funcCallResult = "";
            switch (funcCallName)
            {
                case "CheckFileExist":
                    funcCallResult = FileOperationToolProvider.CheckFileExist(funcParams["filePath"].ToString()).ToString();
                    break;
                case "CheckPathExist":
                    funcCallResult = FileOperationToolProvider.CheckPathExist(funcParams["path"].ToString()).ToString();
                    break;
                case "LoadFile":
                    funcCallResult = FileOperationToolProvider.LoadFile(funcParams["filePath"].ToString());
                    break;
                case "CreateFile":
                    funcCallResult = FileOperationToolProvider.CreateFile(funcParams["filePath"].ToString(), funcParams["fileContent"].ToString());
                    break;
                //case "GetValueFromExecution":
                //    funcCallResult = vsCtrlService.GetValueFromExecution(int.Parse(funcParams["breakPointLine"].ToString()), funcParams["variableName"].ToString());
                //    break;
                case "ExploreDirectory":
                    funcCallResult = FileOperationToolProvider.ExploreDirectory(funcParams["rootDirectory"].ToString(), int.Parse(funcParams["depth"].ToString()));
                    break;
                case "ExecuteCommand":
                    CmdResult cmdResult = ExecuteCommand(funcParams["command"].ToString(), funcParams["workingDirectory"].ToString(), bool.Parse(funcParams["runAsAdmin"].ToString()));
                    funcCallResult = cmdResult.ToString();
                    break;
                case "ExecuteMatlabCommand":
                    funcCallResult = await MatlabControlToolProvider.ExecuteMatlabCommand(funcParams["command"].ToString(),
                        bool.Parse(funcParams["hasReturnValue"].ToString()));
                    break;
                case "ExecutePythonCode":
                    CmdResult pyExecuteResult = PythonToolProvider.ExecutePythonCode(funcParams["scriptContent"].ToString());
                    funcCallResult = pyExecuteResult.ToString();
                    break;
                case "ExecuteTcpCommand":
                    string tcpExecuteResult = TcpToolProvider.ExecuteTcpCommand(Convert.ToString(funcParams["ipAddress"]), Convert.ToInt32(funcParams["port"]), Convert.ToString(funcParams["command"]));
                    funcCallResult = tcpExecuteResult.ToString();
                    break;
                case "ExecuteSshCommand":
                    ((JsonElement)funcParams["port"]).TryGetInt32(out int sshPort);
                    string sshExecuteResult = SshToolProvider.ExecuteSshCommand(Convert.ToString(funcParams["ipAddress"]), Convert.ToString(funcParams["userName"]), Convert.ToString(funcParams["password"]), Convert.ToString(funcParams["command"]), sshPort);
                    funcCallResult = sshExecuteResult.ToString();
                    break;
                case "SendHttpRequestAsync":
                    HttpResult httpResult = await HttpToolProvider.SendHttpRequestAsync(Convert.ToString(funcParams["method"]), Convert.ToString(funcParams["url"]), Convert.ToString(funcParams["content"]));
                    funcCallResult = httpResult.ToString();
                    break;
                case "SendBearerAuthHttpRequestAsync":
                    HttpResult bearerAuthHttpResult = await HttpToolProvider.SendBearerAuthHttpRequestAsync(Convert.ToString(funcParams["method"]), Convert.ToString(funcParams["url"]), Convert.ToString(funcParams["token"]), Convert.ToString(funcParams["content"]));
                    funcCallResult = bearerAuthHttpResult.ToString();
                    break;
                case "SendBasicAuthHttpRequestAsync":
                    HttpResult basicAuthHttpResult = await HttpToolProvider.SendBasicAuthHttpRequestAsync(Convert.ToString(funcParams["method"]), Convert.ToString(funcParams["url"]), Convert.ToString(funcParams["username"]), Convert.ToString(funcParams["password"]), Convert.ToString(funcParams["readAs"]), Convert.ToString(funcParams["content"]));
                    funcCallResult = basicAuthHttpResult.ToString();
                    break;
                case "SendEmail":
                    ((JsonElement)funcParams["port"]).TryGetInt32(out int emailPort);
                    bool sendEmailSuccess = EmailToolProvider.SendEmail(Convert.ToString(funcParams["fromEmail"]), Convert.ToString(funcParams["toEmail"]), Convert.ToString(funcParams["subject"]), Convert.ToString(funcParams["body"]), Convert.ToString(funcParams["smtpServer"]), Convert.ToString(funcParams["username"]), Convert.ToString(funcParams["authorizationCode"]), emailPort);
                    funcCallResult = sendEmailSuccess.ToString();
                    break;
                case "CreateFolder":
                    bool createFolderSuccess = FileOperationToolProvider.CreateFolder(Convert.ToString(funcParams["folderPath"]));
                    funcCallResult = createFolderSuccess.ToString();
                    break;
                case "DeleteFileOrFolderToRecycleBin":
                    bool deleteFileSuccess = FileOperationToolProvider.DeleteFileOrFolderToRecycleBin(Convert.ToString(funcParams["path"]));
                    funcCallResult = deleteFileSuccess.ToString();
                    break;
                default:
                    string errMsg = $"Function {funcCallName} not found";
                    Debug.LogError(errMsg);
                    funcCallResult += errMsg;
                    break;
            }

            return funcCallResult;
        }
        catch (Exception ex)
        {
            return $"Failed to execute toolCall, exception message is: {ex.Message}";
        }

    }
}

public static class FunctionCallExtension
{
    public static List<Dictionary<string, object>> ParseArgumentsAdvanced(this FunctionCall funcCall)
    {
        List<string> argBlocks = ExtractBlocks(funcCall.Arguments);

        List<Dictionary<string, object>> finalArgs = [];
        foreach (string argBlock in argBlocks)
        {
            finalArgs.Add([]);

            foreach (var param in ParseArguments(argBlock))
            {
                finalArgs.Last().Add(param.Key, param.Value);
            }
        }

        return finalArgs;
    }

    private static Dictionary<string, object> ParseArguments(string rawArgsStr)
    {
        Dictionary<string, object>? result = !string.IsNullOrWhiteSpace(rawArgsStr) ? JsonSerializer.Deserialize<Dictionary<string, object>>(rawArgsStr) : null;
        return result ?? [];
    }

    private static List<string> ExtractBlocks(string input)
    {
        List<string> blocks = [];
        int braceCount = 0;
        int start = 0;

        for (int i = 0; i < input.Length; i++)
        {
            if (input[i] == '{')
            {
                if (braceCount == 0)
                {
                    start = i; // Mark the start of a new block
                }
                braceCount++; // Increment count for each opening brace
            }
            else if (input[i] == '}')
            {
                braceCount--; // Decrement count for each closing brace
                if (braceCount == 0)
                {
                    // End of a block
                    blocks.Add(input.Substring(start, i - start + 1));
                }
            }
        }

        return blocks;
    }
}
